package org.simplesocket.simplenettycore.context;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.simplesocket.simplenettycore.config.ConfigLoader;
import org.simplesocket.simplenettycore.exceptions.GlobalExceptionProcessor;
import org.simplesocket.simplenettycore.exceptions.GlobalExceptionHandler;
import org.simplesocket.simplenettycore.exceptions.SimpleNettyGlobalExceptionHandler;
import org.simplesocket.simplenettycore.handlers.Callback;
import org.simplesocket.simplenettycore.handlers.ClientEventCallback;
import org.simplesocket.simplenettycore.handlers.PipelineInitializer;
import org.simplesocket.simplenettycore.handlers.ServerChannelInitializer;
import org.simplesocket.simplenettycore.route.Route;
import org.simplesocket.simplenettycore.route.Router;
import org.simplesocket.simplenettycore.route.RouterEndpoint;
import org.simplesocket.simplenettycore.utils.CommonUtils;
import org.simplesocket.simplenettycore.utils.SimpleNettyUtils;

import java.lang.reflect.Method;
import java.util.Set;

@Slf4j
class ContextHelper {
    
    public static void runServer(SimpleNettyContext context){
        log.info("Starting server ...");
        
        loadClasses(context);
        
        try {
            ChannelFuture channelFuture = new ServerBootstrap()
                    .channel(NioServerSocketChannel.class)
                    .group(context.getSimpleNettyConfig().getBoss(), context.getSimpleNettyConfig().getWorker())
                    .childHandler(new ServerChannelInitializer(context))
                    .bind(context.getSimpleNettyConfig().getHost(),
                            context.getSimpleNettyConfig().getPort())
                    .sync();
            
            log.info("Server has been started at 'tcp://{}:{}'",
                    context.getSimpleNettyConfig().getHost(),
                    context.getSimpleNettyConfig().getPort());
            
            log.info("Listening client's connection ...");
            CommonUtils.printIcon();
            channelFuture.channel().closeFuture().sync();
            log.info("Netty server has been stopped ...");
            
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            context.getSimpleNettyConfig().getBoss().shutdownGracefully();
            context.getSimpleNettyConfig().getWorker().shutdownGracefully();
        }
    }
    
    private static void loadClasses(SimpleNettyContext context){
        // 加载路由
        loadRoute(context);
        // 加载第三方自定义的回调
        loadCallbacks(context);
        // 加载第三方库自定义的配置
        loadUserConfig(context);
        // 加载全局异常处理器
        loadGlobalExceptionHandler(context);
    }
    
    private static void loadRoute(SimpleNettyContext context){
        log.info("Preparing to load route ...");
        
        Set<Class<?>> classes = SimpleNettyUtils.ClassUtil.scanAnnotatedClasses(
                context.getSimpleNettyConfig().getBasePackageName(),
                RouterEndpoint.class);
        for (Class<?> clazz : classes) {
            Object obj;
            try {
                obj = clazz.getDeclaredConstructor().newInstance();
                context.getInstances().put(clazz.getName(), obj);
            }catch (Exception e){
                log.error("Error while instantiating router endpoint class {}\n", clazz.getName());
                System.exit(-1);
                throw new RuntimeException(e);
            }
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method : methods) {
                Route route = method.getAnnotation(Route.class);
                if (route == null){
                    continue;
                }
                String routeUri = route.value();
                Router definedRouter = context.getRouter().get(routeUri);
                if (definedRouter != null){
                    throw new RuntimeException("Load route method error: Route url '"
                            + routeUri + "' define above method '" + method.getName() + "()' in class '"
                            + clazz.getTypeName() + "'is already defined in class '"
                            + definedRouter.getInstance().getClass().getTypeName()
                            + "' method '" + definedRouter.getMethod().getName() + "()'");
                }
                try {
                    Router router = new Router(obj, method);
                    context.getRouter().put(routeUri,router);
                    log.info("Success to load a route method '{}' in class '{}',url is '{}'",
                            method.getName(), clazz.getSimpleName(), routeUri);
                } catch (Exception e) {
                    throw new RuntimeException("Load route method error,because:\n" + e);
                }
            }
        }
        
        log.info("Load route successful ...");
    }
    
    /**
     * 加载一些回调函数，在本库中，有很多的pipeline，pipeline中定义了许多的
     * 出站入站处理器，有些时候库使用者需要在某些出站入站处理器调用时调用一些
     * 库使用者自定义的回调函数，例如ConnectLifecycleHandler，管理连接生
     * 命周期的处理器，它有 开始连接、连接建立、连接断开、断线重连 等事件，
     * 在这些事件发生的时候，库使用者或许会希望做一些操作，于是在ConnectLifecycleHandler
     * 处理器中我们定义了一个成员变量（接口），它有一些方法，用户只要实现了这个
     * 接口，并且在接口上添加@Callback注解，那么程序启动时就会去找接口实现类，
     * 实例化它并将它的实例化对象注入到这个成员变量的值中，那么在ConnectLifecycleHandler
     * 中对成员变量（接口）的方法调用就会调用到库使用者自定义的方法上，从而完成回调
     * 功能，在这里我们就是在将用户实现类实例化并设置到ConnectLifecycleHandler
     * 的成员变量值上
     * @param context 服务上下文
     */
    private static void loadCallbacks(SimpleNettyContext context){
        log.info("Preparing to load user custom callbacks ...");
        
        Set<Class<?>> classes = SimpleNettyUtils.ClassUtil.scanAnnotatedClasses(
                context.getSimpleNettyConfig().getBasePackageName(),
                Callback.class);
        for (Class<?> clazz : classes) {
            if (ClientEventCallback.class.isAssignableFrom(clazz)){
                loadClientEventCallback(clazz);
            }
        }
        
        log.info("Load user custom callbacks successful ...");
    }
    
    private static void loadClientEventCallback(Class<?> clazz){
        try {
            Object callbackObj = clazz.getDeclaredConstructor().newInstance();
            if (callbackObj instanceof ClientEventCallback callback){
                // 这里必须将用户实现类设置在这里，然后ConnectLifecycleHandler不能为单例，否则客户端不能二次连接服务器
                PipelineInitializer.clientEventCallbackWrapper = callback;
                log.info("Custom ClientEventCallback '{}' already loaded ...", clazz.getTypeName());
            }
            
        } catch (Exception e) {
            throw new RuntimeException("Internal exception, please report to the library developer.\n" + e);
        }
    }
    
    private static void loadUserConfig(SimpleNettyContext context){
        log.info("Preparing to load third part component ...");
        for (ConfigLoader configLoader : context.getSimpleNettyConfig().getConfigLoaderList()) {
            try {
                configLoader.load(context);
                log.info("Load third part component '{}' successful ...",configLoader.getClass().getTypeName());
            }catch (Exception e){
                log.error("Load third part config error:\n");
                CommonUtils.printException(e);
                System.exit(-1);
            }
        }
        log.info("Load all third part component successful ...");
    }
    
    private static void loadGlobalExceptionHandler(SimpleNettyContext context){
        log.info("Preparing to load global exception handler ...");
        Set<Class<?>> classes = SimpleNettyUtils.ClassUtil.scanAnnotatedClasses(
                context.getSimpleNettyConfig().getBasePackageName(),
                GlobalExceptionProcessor.class);
        if (!classes.isEmpty()){
            if (classes.size() > 1){
                throw new RuntimeException("The global exception " +
                        "handler can only define one in project ...");
            }else {
                Class<?> clazz = classes.iterator().next();
                try {
                    context.getInstances().put(GlobalExceptionHandler.class.getName(),
                            clazz.getDeclaredConstructor().newInstance());
                    log.info("Success to load third part global exception handler '{}'",
                            clazz.getName());
                } catch (Exception e) {
                    throw new RuntimeException("Load global exception handler error:",e);
                }
            }
        }else {
            context.getInstances().put(GlobalExceptionHandler.class.getName(),
                    new SimpleNettyGlobalExceptionHandler());
        }
        log.info("Load global exception handler successful ...");
    }
    
}
